package maple.core;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ClassNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;

import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

import maple.lang.Command;
import maple.lang.Language;
import maple.util.Profile;
import maple.util.Sensor;
import maple.util.tasks.GatewayTask;
import maple.util.Timestamp;

public class Recorder extends MapleAgent
{
	private Connection _database;
	private PreparedStatement _statement;
	
	private HashMap< String, Integer > _sensors;

	public Recorder()
	{
		super();

		// from MapleAgent
		_min_arguments = 1;
		
		_database = null;
		_statement = null;

		_sensors = new HashMap< String, Integer >();
	}

	protected void setup()
	{
		super.setup();

		if( connect( _arguments[ 0 ].toString() ) )
		{
			check();
		}

		if( _sensors.keySet().size() == 0 )
		{
			doDelete();
		}
	}

	protected void takeDown()
	{
		try
		{
			// close the database connection
			if( _database != null )
			{
				_database.close();
			}
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
	}

	protected void actionInform( final ACLMessage message )
	{
		try
		{
			String received_time = Timestamp.Default.format( new Date( System.currentTimeMillis() ) ); 
			
			String[] tokens = message.getContent().split( Language.DELIMITER );
			if( tokens.length >= 2 )
			{
				String sensed_time = tokens[ 0 ];
				String gateway = message.getSender().getLocalName();
				String port = message.getUserDefinedParameter( Profile.Property.PORT.toString() );
				String sensor = message.getUserDefinedParameter( Profile.Property.TYPE.toString() );
				String location = message.getUserDefinedParameter( Profile.Property.LOCATION.toString() );
				String value = Sensor.toString( sensor, tokens[ 1 ] );

				if( _statement != null )
				{
					_statement.setLong( 1, Long.parseLong( sensed_time ) ); 
					_statement.setString( 2, gateway ); 
					_statement.setString( 3, port ); 
					_statement.setString( 4, sensor ); 
					_statement.setString( 5, location ); 
					_statement.setDouble( 6, Double.parseDouble( value ) ); 
					_statement.setLong( 7, Long.parseLong( received_time ) ); 
					_statement.execute();
					_statement.clearParameters();
				}
			}
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
			_statement = null;
		}
	}

	protected void check()
	{
		HashMap< String, Integer > sensors = new HashMap< String, Integer >();

		HashMap< ServiceDescription, String > services = listReadServices();
		if( services != null )
		{
			for( ServiceDescription service : services.keySet() )
			{
				int interval = getInterval( service.getType() );
				if( interval > 0 )
				{
					String agent = services.get( service );
					String command = Language.Instruction.READ.toString()
									 + Language.DELIMITER
									 + GatewayTask.Property.PORT.toString()
									 + Language.ASSIGN
									 + Language.QUOTE
									 + service.getOwnership()
									 + Language.QUOTE
									 + Language.DELIMITER
									 + GatewayTask.Property.RECEIVER.toString()
									 + Language.ASSIGN
									 + Language.QUOTE
									 + getName()
									 + Language.QUOTE;
					
					String key = agent + Language.DELIMITER + command;
					command = command
							  + Language.DELIMITER
							  + GatewayTask.Property.INTERVAL.toString()
							  + Language.ASSIGN
							  + Language.QUOTE;

					if( _sensors.containsKey( key ) )
					{
						int old_interval = _sensors.get( key );
						if( interval != old_interval )
						{
							// remove the old command
							sendMessage( ACLMessage.REQUEST, agent, command + old_interval + Language.QUOTE, null, null );  
							
							// add the new command
							command = command + interval + Language.QUOTE;
							sendMessage( ACLMessage.REQUEST, agent, command, null, null );  
							
							System.out.println( Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + agent + Language.DELIMITER + command + Language.DELIMITER + Command.Message.OK.toString() );
							_logger.info( agent + Language.DELIMITER + command + Language.DELIMITER + Command.Message.OK.toString() );
						}
					}
					else
					{
						command = command + interval + Language.QUOTE; 	
						sendMessage( ACLMessage.REQUEST, agent, command, null, null );  
						
						System.out.println( Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + agent + Language.DELIMITER + command + Language.DELIMITER + Command.Message.OK.toString() );
						_logger.info( agent + Language.DELIMITER + command + Language.DELIMITER + Command.Message.OK.toString() );
					}

					sensors.put( key, interval ); 
				}
			}
		}
		
		_sensors = sensors;
	}

	/**
	 * return true if connecting is successful
	 *
	 * @param	file_name	the name of the specified file
	 * @return	true if connecting is successful
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	private boolean connect( final String file_name )
	{
		try
		{
			// load the configuration of the database
			BufferedReader reader = new BufferedReader( new FileReader( file_name ) );
			String line = reader.readLine();
			reader.close();

			if( line != null )
			{
				String[] tokens = line.split( Language.DELIMITER );
				if( tokens.length >= 5 )
				{
					// connect to database
					Class.forName( "com.mysql.jdbc.Driver" );
					_database = DriverManager.getConnection( "jdbc:mysql://" + tokens[ 0 ] + ":3306/" + tokens[ 1 ], tokens[ 2 ], tokens[ 3 ] );
					_statement = _database.prepareStatement( "INSERT INTO " + tokens[ 4 ] + " ( sensed_time, gateway, port, sensor, location, value, received_time ) values( ?, ?, ?, ?, ?, ?, ? )" );
					return true;
				}
			}
		}
		catch( ClassNotFoundException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		catch( IOException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return false;
	}

	/**
	 * return the interval of the specified type
	 *
	 * @param	type	the specified type of _sensors
	 * @return	the interval of the specified type
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	private int getInterval( final String type )
	{
		int interval = -1;
		try
		{
			switch( Sensor.Type.valueOf( type ) )
			{
				case PHIDGET_HUMIDITY:
				case PHIDGET_TEMPERATURE:
					interval = 5 * 60;
					break;
				case III_ACTIVEPOWER:
				case III_POWERFACTOR:
				case PHIDGET_LIGHT:
					interval = 10;
					break;
				case PHIDGET_MOTION:
				case PHIDGET_SOUND:
				case PHIDGET_VIBRATION:
					interval = 5;
					break;
				default:
					interval = 10 * 60;
			}

			return 1000 * interval;
		}
		catch( IllegalStateException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return interval;
	}

	/**
	 * return the list of the read service descriptions
	 *
	 * @return	the list of the read service descriptions
	 *
	 * @author	snowfoxy
	 * @since	2011-11-24
	 */
	private HashMap< ServiceDescription, String > listReadServices()
	{
		HashMap< ServiceDescription, String > services = new HashMap< ServiceDescription, String >();
		
		String[] tokens = getName().split( Language.AT );
		HashMap< ServiceDescription, String > candidates = listAllServices( tokens[ 1 ] );
		if( candidates != null )
		{
			for( ServiceDescription candidate : candidates.keySet() )
			{
				if( candidate.getName().equals( Language.Instruction.READ.toString() ) )
				{
					String[] chips = candidate.getOwnership().split( "_" );
					if( chips[ 0 ].equals( "PhidgetInterfaceKit" ) )
					{
					}
					else
					{
						services.put( candidate, candidates.get( candidate ) );
					}
				}
			}
			return services;
		}
		return null;
	}
}
